{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "%matplotlib inline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n\nTrain a Stillinger-Weber potential\n==================================\n\nIn this tutorial, we train a Stillinger-Weber (SW) potential for silicon that is archived\non OpenKIM_.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Before getting started to train the SW model, let's first install the SW model::\n\n   $ kim-api-collections-management install user SW_StillingerWeber_1985_Si__MO_405512056662_005\n\n.. seealso::\n   This installs the model and its driver into the ``User Collection``. See\n   `install_model` for more information about installing KIM models.\n\nWe are going to create potentials for diamond silicon, and fit the potentials to a\ntraining set of energies and forces consisting of compressed and stretched diamond\nsilicon structures, as well as configurations drawn from molecular dynamics trajectories\nat different temperatures.\nDownload the training set :download:`Si_training_set.tar.gz\n<https://raw.githubusercontent.com/mjwen/kliff/master/examples/Si_training_set.tar.gz>`\nand extract the tarball: ``$ tar xzf Si_training_set.tar.gz``. The data is stored in\n**extended xyz** format, and see `doc.dataset` for more information of this format.\n\n<div class=\"alert alert-danger\"><h4>Warning</h4><p>The ``Si_training_set`` is just a toy data set for the purpose to demonstrate how to\n   use KLIFF to train potentials. It should not be used to train any potential for real\n   simulations.</p></div>\n\nLet's first import the modules that will be used in this example.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "from kliff.models import KIM\nfrom kliff.loss import Loss\nfrom kliff.calculators import Calculator\nfrom kliff.dataset import Dataset"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Model\n-----\n\nWe first create a KIM model for the SW potential, and print out all the available\nparameters that can be optimized (we call this ``model parameters``).\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "model = KIM(model_name=\"SW_StillingerWeber_1985_Si__MO_405512056662_005\")\nmodel.echo_model_params()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The output is generated by the last line, and it tells us the ``name``, ``value``,\n``size``, ``data type`` and a ``description`` of each parameter.\n\n<div class=\"alert alert-info\"><h4>Note</h4><p>You can provide a ``path`` argument to the method ``echo_model_params(path)`` to\n   write the available parameters information to a file indicated by ``path``.</p></div>\n\n<div class=\"alert alert-info\"><h4>Note</h4><p>The available parameters information can also by obtained using the **kliff**\n   `cmdlntool`:\n   ``$ kliff model --echo-params SW_StillingerWeber_1985_Si__MO_405512056662_005``</p></div>\n\nNow that we know what parameters are available for fitting, we can optimize all or a\nsubset of them to reproduce the training set.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "model.set_fitting_params(\n    A=[[5.0, 1.0, 20]], B=[[\"default\"]], sigma=[[2.0951, \"fix\"]], gamma=[[1.5]]\n)\nmodel.echo_fitting_params()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Here, we tell KLIFF to fit four parameters ``B``, ``gamma``, ``sigma``, and ``A`` of the\nSW model. The information for each fitting parameter should be provided as a list of\nlist, where the size of the outer list should be equal to the ``size`` of the parameter\ngiven by ``model.echo_model_params()``. For each inner list, you can provide either one,\ntwo, or three items.\n\n- One item. You can use a numerical value (e.g. ``gamma``) to provide an initial guess\n  of the parameter. Alternatively, the string ``'default'`` can be provided to use the\n  default value in the model (e.g. ``B``).\n\n- Two items. The first item should be a numerical value and the second item should be\n  the string ``'fix'`` (e.g. ``sigma``), which tells KLIFF to use the value for the\n  parameter, but do not optimize it.\n\n- Three items. The first item can be a numerical value or the string ``'default'``,\n  having the same meanings as the one item case. In the second and third items, you can\n  list the lower and upper bounds for the parameters, respectively. A bound could be\n  provided as a numerical values or ``None``. The latter indicates no bound is applied.\n\nThe call of ``model.echo_fitting_params()`` prints out the fitting parameters that we\nrequire KLIFF to optimize. The number ``1`` after the name of each parameter indicates\nthe size of the parameter.\n\n<div class=\"alert alert-info\"><h4>Note</h4><p>The parameters that are not included as a fitting parameter are fixed to the default\n   values in the model during the optimization.</p></div>\n\n\nTraining set\n------------\n\nKLIFF has a :class:`~kliff.dataset.Dataset` to deal with the training data (and possibly\ntest data). For the silicon training set, we can read and process the files by:\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "dataset_name = \"Si_training_set\"\ntset = Dataset()\ntset.read(dataset_name)\nconfigs = tset.get_configs()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The ``configs`` in the last line is a list of :class:`~kliff.dataset.Configuration`.\nEach configuration is an internal representation of a processed **extended xyz** file,\nhosting the species, coordinates, energy, forces, and other related information of a\nsystem of atoms.\n\n\nCalculator\n----------\n\n:class:`~kliff.calculator.Calculator` is the central agent that exchanges information\nand orchestrate the operation of the fitting process. It calls the model to compute the\nenergy and forces and provide this information to the `Loss function`_ (discussed below)\nto compute the loss. It also grabs the parameters from the optimizer and update the\nparameters stored in the model so that the up-to-date parameters are used the next time\nthe model is evaluated to compute the energy and forces. The calculator can be created\nby:\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "calc = Calculator(model)\ncalc.create(configs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "where ``calc.create(configs)`` does some initializations for each\nconfiguration in the training set, such as creating the neighbor list.\n\n\nLoss function\n-------------\n\nKLIFF uses a loss function to quantify the difference between the training set data and\npotential predictions and uses minimization algorithms to reduce the loss as much as\npossible. KLIFF provides a large number of minimization algorithms by interacting with\nSciPy_. For physics-motivated potentials, any algorithm listed on\n`scipy.optimize.minimize`_ and `scipy.optimize.least_squares`_ can be used. In the\nfollowing code snippet, we create a loss of energy and forces, where the residual\nfunction uses an ``energy_weight`` of ``1.0`` and a ``forces_weight`` of ``0.1``, and\n``2`` processors will be used to calculate the loss. The ``L-BFGS-B`` minimization\nalgorithm is applied to minimize the loss, and the minimization is allowed to run for\na max number of 100 iterations.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "steps = 100\nresidual_data = {\"energy_weight\": 1.0, \"forces_weight\": 0.1}\nloss = Loss(calc, residual_data=residual_data, nprocs=2)\nloss.minimize(method=\"L-BFGS-B\", options={\"disp\": True, \"maxiter\": steps})"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The minimization stops after running for 27 steps.  After the minimization, we'd better\nsave the model, which can be loaded later for the purpose to do a retraining or\nevaluations. If satisfied with the fitted model, you can also write it as a KIM model\nthat can be used with LAMMPS_, GULP_, ASE_, etc. via the kim-api_.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "model.echo_fitting_params()\nmodel.save(\"kliff_model.pkl\")\nmodel.write_kim_model()\nmodel.load(\"kliff_model.pkl\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The first line of the above code generates the output.  A comparison with the original\nparameters before carrying out the minimization shows that we recover the original\nparameters quite reasonably. The second line saves the fitted model to a file named\n``kliff_model.pkl`` on the disk, and the third line writes out a KIM potential named\n``SW_StillingerWeber_1985_Si__MO_405512056662_005_kliff_trained``.\n\n.. seealso::\n   For information about how to load a saved model, see `doc.modules`.\n\n\n\n"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.7.4"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}